26:25.129 Web版常见问题参考: https://ask.dcloud.net.cn/article/35232
18:26:25.129 ready in 1370ms.
18:26:26.409 [vite] connecting...
18:26:26.413 [vite] connected.
18:26:27.362 App Launch at App.vue:4
18:26:27.894 App Show at App.vue:7
18:26:28.389 获取评论数据失败: {errMsg: "request:fail"} at pages/index/index.vue:70
18:26:33.461 App Hide at App.vue:10
<template>
	<view class="content">
		<view class="header">
			<image class="book-cover" src="/static/book-cover.jpg"></image>
			<view class="book-info">
				<text class="book-title">极速探索HarmonyOS NEXT：纯血鸿蒙应用开发实践</text>
				<text class="book-subtitle">豆瓣读书评论</text>
			</view>
		</view>

		<view class="reviews-container">
			<view v-for="(item, index) in reviewList" :key="index" class="review-item">
				<view class="review-header">
					<text class="review-title">{{ item.title }}</text>
					<text class="review-rating" v-if="item.rating">{{ item.rating }}</text>
				</view>
				<view class="review-author">
					<text>作者: {{ item.author }}</text>
					<text class="review-date">{{ item.date }}</text>
				</view>
				<view class="review-content">
					<text>{{ item.content }}</text>
				</view>
				<view class="review-link">
					<text @click="openLink(item.link)">查看完整评论</text>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
export default {
	data() {
		return {
			title: '极速探索HarmonyOS NEXT',
			reviewList: []
		}
	},
	onLoad() {
		// 页面加载时获取豆瓣评论数据
		this.fetchReviews();
	},
	methods: {
		async fetchReviews(retryCount = 3) {
			try {
				// 首先尝试从本地缓存获取数据
				const cachedData = uni.getStorageSync('reviewsCache');
				const cacheTime = uni.getStorageSync('reviewsCacheTime');
				const cacheExpiry = 3600000; // 缓存有效期1小时

				if (cachedData && cacheTime && (Date.now() - cacheTime < cacheExpiry)) {
					console.log('从缓存加载数据');
					this.reviewList = JSON.parse(cachedData);
					return;
				}

				const response = await uni.request({
					url: 'https://api.allorigins.win/raw?url=' + encodeURIComponent('https://www.douban.com/feed/subject/37196724/reviews'),
					method: 'GET',
					dataType: 'text',
					header: {
						'Accept': 'application/xml',
						'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
						'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
						'Cache-Control': 'no-cache',
						'Pragma': 'no-cache'
					},
					timeout: 15000
				});

				if (!response.statusCode || response.statusCode !== 200) {
					throw new Error(`请求失败: ${response.statusCode || '网络错误'}`);
				}

				if (!response.data) {
					throw new Error('返回数据为空');
				}

				// 使用简单的字符串处理方法解析XML数据
				const xmlStr = response.data;
				const reviews = [];
				const itemRegex = /<item>([\s\S]*?)<\/item>/g;
				let match;

				while ((match = itemRegex.exec(xmlStr)) !== null) {
					const itemContent = match[1];
					const getTagContent = (tag) => {
						const regex = new RegExp(`<${tag}[^>]*>([\s\S]*?)<\/${tag}>`);
						const match = itemContent.match(regex);
						return match ? match[1].trim() : '';
					};

					const title = getTagContent('title') || '无标题';
					const cleanTitle = title.replace(/《极速探索 HarmonyOS NEXT》书评 \(评论: (.+)\)/, '$1');
					const author = getTagContent('dc:creator') || '匿名';
					const pubDate = getTagContent('pubDate');
					const description = getTagContent('description');
					const cleanDescription = description
						.replace(/<\!\[CDATA\[(.*?)\]\]>/g, '$1')
						.replace(/\{[^}]*\}/g, '')
						.replace(/\[[^\]]*\]/g, '')
						.replace(/[\u0000-\u001F\u007F-\u009F]/g, '')
						.replace(/&[^;]+;/g, '')
						.replace(/\s+/g, ' ')
						.trim();
					const link = getTagContent('link');

					const ratingMatch = title.match(/(力荐|推荐|还行|较差|很差)/);
					const rating = ratingMatch ? ratingMatch[1] : '';

					reviews.push({
						title: cleanTitle.trim(),
						author: author.trim(),
						date: new Date(pubDate).toLocaleDateString('zh-CN'),
						content: cleanDescription,
						link: link,
						rating: rating
					});
				}

				if (reviews.length === 0) {
					throw new Error('未找到评论数据');
				}

				this.reviewList = reviews;

				// 将数据存入本地缓存
				uni.setStorageSync('reviewsCache', JSON.stringify(reviews));
				uni.setStorageSync('reviewsCacheTime', Date.now());
			} catch (error) {
				console.error('获取评论数据失败:', error);

				// 如果有缓存数据，在请求失败时使用缓存
				const cachedData = uni.getStorageSync('reviewsCache');
				if (cachedData) {
					console.log('请求失败，使用缓存数据');
					this.reviewList = JSON.parse(cachedData);
					uni.showToast({
						title: '使用缓存数据显示',
						icon: 'none',
						duration: 2000
					});
					return;
				}

				if (retryCount > 0) {
					console.log(`重试获取数据，剩余重试次数: ${retryCount - 1}`);
					await new Promise(resolve => setTimeout(resolve, 2000));
					return this.fetchReviews(retryCount - 1);
				}

				uni.showToast({
					title: `获取评论数据失败: ${error.message || '网络错误'}`,
					icon: 'none',
					duration: 3000
				});
			}
		},
	},
	methods: {
		async fetchReviews(retryCount = 3) {
			try {
				// 首先尝试从本地缓存获取数据
				const cachedData = uni.getStorageSync('reviewsCache');
				const cacheTime = uni.getStorageSync('reviewsCacheTime');
				const cacheExpiry = 3600000; // 缓存有效期1小时

				if (cachedData && cacheTime && (Date.now() - cacheTime < cacheExpiry)) {
					console.log('从缓存加载数据');
					this.reviewList = JSON.parse(cachedData);
					return;
				}

				const response = await uni.request({
					url: 'https://api.allorigins.win/raw?url=' + encodeURIComponent('https://www.douban.com/feed/subject/37196724/reviews'),
					method: 'GET',
					dataType: 'text',
					header: {
						'Accept': 'application/xml',
						'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
						'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
						'Cache-Control': 'no-cache',
						'Pragma': 'no-cache'
					},
					timeout: 15000
				});

				if (!response.statusCode || response.statusCode !== 200) {
					throw new Error(`请求失败: ${response.statusCode || '网络错误'}`);
				}

				if (!response.data) {
					throw new Error('返回数据为空');
				}

				// 解析XML数据
				const parser = new DOMParser();
				const xmlDoc = parser.parseFromString(response.data, 'text/xml');
				const items = xmlDoc.getElementsByTagName('item');

				if (!items || items.length === 0) {
					throw new Error('未找到评论数据');
				}

				const reviews = Array.from(items).map(item => {
					const title = item.getElementsByTagName('title')[0]?.textContent || '无标题';
					const cleanTitle = title.replace(/《极速探索 HarmonyOS NEXT》书评 \(评论: (.+)\)/, '$1');
					const author = item.getElementsByTagName('dc:creator')[0]?.textContent || '匿名';
					const pubDate = item.getElementsByTagName('pubDate')[0]?.textContent;
					const description = item.getElementsByTagName('description')[0]?.textContent;
					const cleanDescription = description ? description
						.replace(/<\!\[CDATA\[(.*?)\]\]>/g, '$1')
						.replace(/\{[^}]*\}/g, '')
						.replace(/\[[^\]]*\]/g, '')
						.replace(/[\u0000-\u001F\u007F-\u009F]/g, '')
						.replace(/&[^;]+;/g, '')
						.replace(/\s+/g, ' ')
						.trim() : '';
					const link = item.getElementsByTagName('link')[0]?.textContent;

					const ratingMatch = title.match(/(力荐|推荐|还行|较差|很差)/);
					const rating = ratingMatch ? ratingMatch[1] : '';

					return {
						title: cleanTitle.trim(),
						author: author.trim(),
						date: new Date(pubDate).toLocaleDateString('zh-CN'),
						content: cleanDescription,
						link: link,
						rating: rating
					};
				});

				this.reviewList = reviews;

				// 将数据存入本地缓存
				uni.setStorageSync('reviewsCache', JSON.stringify(reviews));
				uni.setStorageSync('reviewsCacheTime', Date.now());
			} catch (error) {
				console.error('获取评论数据失败:', error);

				// 如果有缓存数据，在请求失败时使用缓存
				const cachedData = uni.getStorageSync('reviewsCache');
				if (cachedData) {
					console.log('请求失败，使用缓存数据');
					this.reviewList = JSON.parse(cachedData);
					uni.showToast({
						title: '使用缓存数据显示',
						icon: 'none',
						duration: 2000
					});
					return;
				}

				if (retryCount > 0) {
					console.log(`重试获取数据，剩余重试次数: ${retryCount - 1}`);
					await new Promise(resolve => setTimeout(resolve, 2000));
					return this.fetchReviews(retryCount - 1);
				}

				uni.showToast({
					title: `获取评论数据失败: ${error.message || '网络错误'}`,
					icon: 'none',
					duration: 3000
				});
			}
		},
		openLink(url) {
			// #ifdef H5
			window.open(url);
			// #endif

			// #ifdef APP-PLUS
			plus.runtime.openURL(url);
			// #endif

			// #ifdef MP
			uni.setClipboardData({
				data: url,
				success: () => {
					uni.showToast({
						title: '链接已复制，请在浏览器中打开',
						icon: 'none'
					});
				}
			});
			// #endif
		}
	}
}

</script>

<style>
.content {
	padding: 20rpx;
}

.header {
	display: flex;
	padding: 20rpx;
	margin-bottom: 30rpx;
	background-color: #f8f8f8;
	border-radius: 10rpx;
}

.book-cover {
	width: 200rpx;
	height: 280rpx;
	border-radius: 8rpx;
}

.book-info {
	margin-left: 20rpx;
	display: flex;
	flex-direction: column;
	justify-content: center;
	flex: 1;
}

.book-title {
	font-size: 32rpx;
	font-weight: bold;
	margin-bottom: 10rpx;
	color: #333;
}

.book-subtitle {
	font-size: 28rpx;
	color: #666;
}

.reviews-container {
	margin-top: 20rpx;
}

.review-item {
	padding: 20rpx;
	margin-bottom: 20rpx;
	background-color: #fff;
	border-radius: 10rpx;
	box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}

.review-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 10rpx;
}

.review-title {
	font-size: 30rpx;
	font-weight: bold;
	color: #333;
	flex: 1;
}

.review-rating {
	font-size: 26rpx;
	color: #ff6b81;
	padding: 4rpx 12rpx;
	background-color: #fff5f6;
	border-radius: 6rpx;
}

.review-author {
	display: flex;
	justify-content: space-between;
	font-size: 26rpx;
	color: #666;
	margin-bottom: 10rpx;
}

.review-date {
	color: #999;
}

.review-content {
	font-size: 28rpx;
	color: #333;
	line-height: 1.6;
	margin: 16rpx 0;
}

.review-link {
	text-align: right;
}

.review-link text {
	font-size: 26rpx;
	color: #007aff;
}
</style>
